<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">

	<style>
		body {
			overflow: hidden;
		}

		#content {
			overflow-y: auto;
			height: 100%;
			padding-left: 10px;
			padding-right: 10px;
		}

		mark {
			background: #CF3F00;
			color: white;
		}

		ul ul, ul ol, ol ul, ol ol   {
			margin-bottom: 0px;			
		}

		.katex { font-size: 1.3em; } /* This controls the global Katex font size*/
	</style>
</head>

<body id="body">
	<div id="hlScriptContainer"></div>
	<div id="markScriptContainer"></div>
	<div id="content" ondragstart="return false;" ondrop="return false;"></div>

	<script>
	const contentElement = document.getElementById('content');

	const ipc = {};

	window.addEventListener('message', (event) => {
		// Here we only deal with messages that are sent from the main Electro process to the webview.
		if (!event.data || event.data.target !== 'webview') return;

		const callName = event.data.name;
		const callData = event.data.data;

		if (!ipc[callName]) {
			console.warn('Missing IPC function:', event.data);
		} else {
			ipc[callName](callData);
		}
	});

	// ----------------------------------------------------------------------
	// Handle dynamically loading HLJS when a code element is present
	// ----------------------------------------------------------------------

	let hljsScriptAdded = false;
	let hljsLoaded = false;

	function loadHljs(options) {
		hljsScriptAdded = true;

		const script = document.createElement('script');
		script.onload = function () {
			hljsLoaded = true;
			applyHljs();
		};
		script.src = 'highlight/highlight.pack.js';
		document.getElementById('hlScriptContainer').appendChild(script);

		const link = document.createElement('link');
		link.rel = 'stylesheet';
		// https://ace.c9.io/build/kitchen-sink.html
		// https://highlightjs.org/static/demo/
		link.href = 'highlight/styles/' + options.codeTheme;
		document.getElementById('hlScriptContainer').appendChild(link);
	}

	function loadAndApplyHljs(options) {
		var codeElements = document.getElementsByClassName('code');
		if (!codeElements.length) return;

		if (!hljsScriptAdded) {
			this.loadHljs(options);
			return;
		}

		// If HLJS is not loaded yet, no need to do anything. When it loads
		// it will automatically apply the style to all the code elements.
		if (hljsLoaded) applyHljs(codeElements);
	}

	function applyHljs(codeElements) {
		if (typeof codeElements === 'undefined') codeElements = document.getElementsByClassName('code');

		for (var i = 0; i < codeElements.length; i++) {
			try {
				hljs.highlightBlock(codeElements[i]);
			} catch (error) {
				console.warn('Cannot highlight code', error);
			}
		}
	}

	// ----------------------------------------------------------------------
	// / Handle dynamically loading HLJS when a code element is present
	// ----------------------------------------------------------------------

	// Note: the scroll position source of truth is "percentScroll_". This is easier to manage than scrollTop because
	// the scrollTop value depends on the images being loaded or not. For example, if the scrollTop is saved while
	// images are being displayed then restored while images are being reloaded, the new scrollTop might be changed
	// so that it is not greater than contentHeight. On the other hand, with percentScroll it is possible to restore
	// it at any time knowing that it's not going to be changed because the content height has changed.
	// To restore percentScroll the "checkScrollIID" interval is used. It constantly resets the scroll position during
	// one second after the content has been updated.
	//
	// ignoreNextScroll is used to differentiate between scroll event from the users and those that are the result
	// of programmatically changing scrollTop. We only want to respond to events initiated by the user.

	let percentScroll_ = 0;
	let checkScrollIID_ = null;

	function setPercentScroll(percent) {
		percentScroll_ = percent;
		contentElement.scrollTop = percentScroll_ * maxScrollTop();
	}

	function percentScroll() {
		return percentScroll_;
	}

	function restorePercentScroll() {
		setPercentScroll(percentScroll_);
	}

	ipc.setHtml = (event) => {
		const html = event.html;

		updateBodyHeight();

		contentElement.innerHTML = html;

		loadAndApplyHljs(event.options);

		// Remove the bullet from "ul" for checkbox lists and extra padding
		// const checkboxes = document.getElementsByClassName('checkbox');
		// for (let i = 0; i < checkboxes.length; i++) {
		// 	const cb = checkboxes[i];
		// 	const ul = cb.parentElement.parentElement;
		// 	if (!ul) {
		// 		console.warn('Unexpected layout for checkbox');
		// 		continue;
		// 	}
		// 	ul.style.listStyleType = 'none';
		// 	ul.style.paddingLeft = 0;
		// }

		let previousContentHeight = contentElement.scrollHeight;
		let startTime = Date.now();
		ignoreNextScrollEvent = true;
		restorePercentScroll();

		if (!checkScrollIID_) {
			checkScrollIID_ = setInterval(() => {
				const h = contentElement.scrollHeight;
				if (h !== previousContentHeight) {
					previousContentHeight = h;
					ignoreNextScrollEvent = true;
					restorePercentScroll();
				}
				if (Date.now() - startTime >= 1000) {
					clearInterval(checkScrollIID_);
					checkScrollIID_ = null;
				}
			}, 1);
		}
	}

	let ignoreNextScrollEvent = false;
	ipc.setPercentScroll = (event) => {
		const percent = event.percent;

		if (checkScrollIID_) {
			clearInterval(checkScrollIID_);
			checkScrollIID_ = null;
		}

		ignoreNextScrollEvent = true;
		setPercentScroll(percent);
	}

	let mark_ = null;
	function setMarkers(keywords) {
		if (!mark_) {
			mark_ = new Mark(document.getElementById('content'), {
				exclude: ['img'],
				acrossElements: true,
			});
		}

		mark_.mark(keywords);
	}

	let markLoaded_ = false;
	ipc.setMarkers = (event) => {
		const keywords = event.keywords;

		if (!keywords.length && !markLoaded_) return;

		if (!markLoaded_) {
			const script = document.createElement('script');
			script.onload = function() {
				setMarkers(keywords);
			};

			script.src = '../../node_modules/mark.js/dist/mark.min.js';
			document.getElementById('markScriptContainer').appendChild(script);
			markLoaded_ = true;
		} else {
			setMarkers(keywords);
		}
	}

	function maxScrollTop() {
		return Math.max(0, contentElement.scrollHeight - contentElement.clientHeight);
	}

	// The body element needs to have a fixed height for the content to be scrollable
	function updateBodyHeight() {
		document.getElementById('body').style.height = window.innerHeight + 'px';
	}

	const ipcProxySendToHost = (methodName, arg) => {
		window.postMessage({ target: 'main', name: methodName, args: [ arg ] }, '*');
	}

	contentElement.addEventListener('scroll', function(e) {
		if (ignoreNextScrollEvent) {
			ignoreNextScrollEvent = false;
			return;
		}
		const m = maxScrollTop();
		const percent = m ? contentElement.scrollTop / m : 0;
		setPercentScroll(percent);
		
		ipcProxySendToHost('percentScroll', percent);
	});

	document.addEventListener('contextmenu', function(event) {
		let element = event.target;

		// To handle right clicks on resource icons
		if (element && !element.getAttribute('data-resource-id')) element = element.parentElement;

		if (element && element.getAttribute('data-resource-id')) {
			ipcProxySendToHost('contextMenu', {
				type: element.getAttribute('src') ? 'image' : 'resource',
				resourceId: element.getAttribute('data-resource-id'),
			});
		} else {
			const selectedText = window.getSelection().toString();

			if (selectedText) {
				ipcProxySendToHost('contextMenu', {
					type: 'text',
					textToCopy: selectedText,
				});
			} else if (event.target.getAttribute('href')) {
				ipcProxySendToHost('contextMenu', {
					type: 'link',
					textToCopy: event.target.getAttribute('href'),
				});
			}
		}
	});

	function handleInternalLink(event, anchorNode) {
		const href = anchorNode.getAttribute('href');
		if (href.indexOf('#') === 0) {
			event.preventDefault();
			location.hash = href;
			return true;
		}
		return false;
	}

	document.addEventListener('click', function(event) {
		const t = event.target;

		// Prevent URLs added via <a> tags from being opened within the application itself
		// otherwise it would open the whole website within the WebView.
		if (t && t.nodeName === 'A' && !t.hasAttribute('data-from-md')) {
			if (handleInternalLink(event, t)) return;

			event.preventDefault();
			ipcProxySendToHost(t.getAttribute('href'));
			return;
		}

		// If this is an internal link, jump to the anchor directly
		if (t && t.nodeName === 'A' && t.hasAttribute('data-from-md')) {
			if (handleInternalLink(event, t)) return;
		}
	});

	// Disable drag and drop otherwise it's possible to drop a URL
	// on it and it will open in the view as a website.
	document.addEventListener('drop', function(e) {
		e.preventDefault();
		e.stopPropagation();
	});
	document.addEventListener('dragover', function(e) {
		e.preventDefault();
		e.stopPropagation();
	});
	document.addEventListener('dragover', function(e) {
		e.preventDefault();
	});

	window.addEventListener('resize', function() {
		updateBodyHeight();
	});

	updateBodyHeight();	
	</script>

</body>
</html>
